﻿using System;
using System.Net;

using System.Collections.Generic;
using caedisi.Web.DataModel;
using System.ComponentModel;
using caedisi.Common;
using System.Linq;
using System.Collections.ObjectModel;
using caedisi.Helpers;

namespace caedisi.Models
{
    /// <summary>
    /// The simulation class which is responsible to store the simulation properties and the algorihtm to process the execution
    /// </summary>
    public class InverterExecution : INotifyPropertyChanged
    {
        /// <summary>InverterExecution
        /// Gets the cellular automata.
        /// </summary>
        public CellularAutomata CellularAutomata
        {
            get;
            private set;
        }

        private int _CurrentStep;
        /// <summary>
        /// Gets or sets the current step.
        /// </summary>
        /// <value>
        /// The current step.
        /// </value>
        public int CurrentStep 
        {
            get
            {
                return _CurrentStep;
            }
            set
            {
                if (_CurrentStep != value)
                {
                    int oldCurrentStep = _CurrentStep;
                    _CurrentStep = value;
                    this.RaisePropertyChanged("CurrentStep");
                    OnCurrentStepChanged(oldCurrentStep, value);

                }
            }
        }


        private int _LastStep;
        /// <summary>
        /// Gets or sets the current step.
        /// </summary>
        /// <value>
        /// The current step.
        /// </value>
        public int LastStep
        {
            get
            {
                return _LastStep;
            }
            set
            {
                if (_LastStep != value)
                {
                    int oldLastStep = _LastStep;
                    _LastStep = value;
                    this.RaisePropertyChanged("LastStep");
                    OnLastStepChanged(oldLastStep, value);

                }
            }
        }

        private bool _IsLastStepSet = false;
        /// <summary>
        /// Gets or sets the current step.
        /// </summary>
        /// <value>
        /// The current step.
        /// </value>
        public bool IsLastStepSet
        {
            get
            {
                return _IsLastStepSet;
            }
            set
            {
                if (_IsLastStepSet != value)
                {
                    _IsLastStepSet = value;
                    this.RaisePropertyChanged("IsLastStepSet");
                }
            }
        }

        private void OnLastStepChanged(int oldLastStep, int value)
        {
            //TODO. Delete all extra data after last step
        }
              
                
        private int _OutOfBoundCellStateTypeId = (int) EnumCellStateType.Decontaminated;
        /// <summary>
        /// Gets or sets the _ out of bound cell state type id. This is a state that the cell will see when they will request
        /// a neighbour that falls outside the cellular automata
        /// </summary>
        /// <value>
        /// The _ out of bound cell state type id.
        /// </value>
        public int OutOfBoundCellStateTypeId
        {
            get
            {
                return _OutOfBoundCellStateTypeId;
            }
            set
            {
                if (_OutOfBoundCellStateTypeId != value)
                {
                    _OutOfBoundCellStateTypeId = value;
                    this.RaisePropertyChanged("OutOfBoundCellStateTypeId");
                }
            }
        }

        private bool _IsDecontaminatingCleanInNextStep = true;
        /// <summary>
        /// Gets or sets the _ out of bound cell state type id. This is a state that the cell will see when they will request
        /// a neighbour that falls outside the cellular automata
        /// </summary>
        /// <value>
        /// The _ out of bound cell state type id.
        /// </value>
        public bool IsDecontaminatingCleanInNextStep
        {
            get
            {
                return _IsDecontaminatingCleanInNextStep;
            }
            set
            {
                if (_IsDecontaminatingCleanInNextStep != value)
                {
                    _IsDecontaminatingCleanInNextStep = value;
                    this.RaisePropertyChanged("IsDecontaminatingCleanInNextStep");
                }
            }
        }

        

        /// <summary>
        /// Gets the system state dictionary. Contains a collection of all the state of each cellular automata cell during the entire process.
        /// The data structure of the SystemStateDictionary
        ///  - the key is a pair of of cell id and time 
        ///  - the value is an object SimulationIterationItem that stores the state of the cell and the transition rules that causes the state of the cell
        /// </summary>
        public Dictionary<Tuple<Guid, int>, SimulationIterationItem> SystemStateDictionary { get; private set; }

        public Dictionary<int, InvertedCellItem[, ,]> TemporalCellularAutomata { get; private set; }


        /// <summary>
        /// Prevents a default instance of the <see cref="InverterExecution"/> class from being created.
        /// </summary>
        private InverterExecution()
        {
            SystemStateDictionary = new Dictionary<Tuple<Guid, int>, SimulationIterationItem>();
            TemporalCellularAutomata = new Dictionary<int, InvertedCellItem[, ,]>();
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="InverterExecution"/> class.
        /// </summary>
        /// <param name="cellularAutomata">The cellular automata.</param>
        public InverterExecution(CellularAutomata cellularAutomata)
            : this()
        {            
            this.CellularAutomata = cellularAutomata;
            this.CurrentStep = 0;
            this.SystemStateDictionary.Clear();
            this.TemporalCellularAutomata.Clear();

            //cellularAutomata.CurrentSimulation = this;
        }


        public void StoreStates()
        {
            int step = this.CurrentStep;

            int numberOfRows = this.CellularAutomata.Height;
            int numberOfColumns = this.CellularAutomata.Width;

            InvertedCellItem[, ,] invertedCells = new InvertedCellItem[numberOfRows, numberOfColumns, 1];

            for (int i = 0; i < numberOfRows; i++)
            {
                for (int j = 0; j < numberOfColumns; j++)
                {
                    int k = 0;  // only 2D for now

                    Cell myCellFromUI = this.CellularAutomata.CellularAutomataAsArray[i, j, k];

                    //using the system state dictionary mode
                    SimulationIterationItem iterationItem = GetSimulationIterationItem(myCellFromUI.CellId, step, true);
                    iterationItem.Reset();
                    iterationItem.CellStateTypeId = myCellFromUI.CellStateTypeId;

                    Tuple<Guid, int> newIterationItemKey = new Tuple<Guid, int>(myCellFromUI.CellId, step);
                    this.SystemStateDictionary[newIterationItemKey] = iterationItem;

                    invertedCells[i, j, k] = new InvertedCellItem(myCellFromUI.CellId, myCellFromUI.CellStateTypeId);

                    //immunity handling
                    if (this.CellularAutomata.ImmunityTime != 0)
                    {
                        if (invertedCells[i, j, k].CellStateTypeId == ((int)EnumCellStateType.Decontaminating))
                        {
                            invertedCells[i, j, k].ImmunityTimerValue = this.CellularAutomata.ImmunityTime;
                        }
                        else if (invertedCells[i, j, k].CellStateTypeId == ((int)EnumCellStateType.Decontaminated))
                        {
                            if (invertedCells[i, j, k].ImmunityTimerValue>0)
                                invertedCells[i, j, k].ImmunityTimerValue--;
                        }

                    }

                }
            }

            this.TemporalCellularAutomata[step] = invertedCells;
        }

        /// <summary>
        /// Called when [current step changed].
        /// </summary>
        /// <param name="previousStep">The previous step.</param>
        /// <param name="newStep">The new step.</param>
        private void OnCurrentStepChanged(int previousStep, int newStep)
        {            
             IEnumerable<KeyValuePair<Tuple<Guid,int>, SimulationIterationItem >> filteredDictionary = this.SystemStateDictionary.Where(KeyValuePair => KeyValuePair.Key.Item2==newStep);
            //if step were already stored
             if (filteredDictionary != null && filteredDictionary.Count() > 0)
             {
                 RefreshCellularAutomataCells(newStep);
             }             
         }

        /// <summary>
        /// Processes the transition rules algorithm.
        /// </summary>
        /// <param name="cellularAutomata">The cellular automata.</param>
        /// <param name="newStep">The new step.</param>
        private void ProcessTransitionRulesAlgorithm(Web.DataModel.CellularAutomata cellularAutomata, int newStep)
        {
            int numberOfRows = cellularAutomata.Height;
            int numberOfColumns = cellularAutomata.Width;

            for (int i = 0; i < numberOfRows; i++)
            {
                for (int j = 0; j < numberOfColumns; j++)
                {
                    int k = 0;  // only 2D for now

                    Cell myCell = cellularAutomata.CellularAutomataAsArray[i, j, k];

                    SimulationIterationItem previousIterationItem = GetSimulationIterationItem(myCell.CellId, newStep-1, false);
                    SimulationIterationItem newIterationItem = GetSimulationIterationItem(myCell.CellId, newStep, true);
                    newIterationItem.Reset();

                    //grab which rules applies to myCell
                    foreach (TransitionRule rule in cellularAutomata.TransitionRules)
                    {
                        bool doesCellCurrentStateSatisfyRule = DoesCellConfigurationSatisfyTransitionRule(myCell, rule, cellularAutomata);
                        if (doesCellCurrentStateSatisfyRule)
                            newIterationItem.TransitionRulesCause.Add(rule);
                    }
                    
                    //by default, the result state is the old value
                    int resultCellStateTypeId = myCell.CellStateTypeId;

                    if (newIterationItem.TransitionRulesCause.Count > 0)
                    {
                        if (newIterationItem.IsDeterministic)
                        {
                            int tentativeResultCellStateTypeId = newIterationItem.TransitionRulesCause.First().ResultCellStateTypeId;

                            if (tentativeResultCellStateTypeId == ((int)EnumCellStateType.Decontaminated))
                            {
                                resultCellStateTypeId = ((int)EnumCellStateType.Decontaminated);
                                newIterationItem.ImmunityTimerValue = cellularAutomata.ImmunityTime;  //each time the cell gets decontaminated (i.e the virus has been cleant), the immunity timer is reset
                            }
                            else if (tentativeResultCellStateTypeId == ((int)EnumCellStateType.Contaminated))
                            {
                                //it will goes to contaminated if the cell is no more immune
                                if (previousIterationItem == null || previousIterationItem.ImmunityTimerValue == 0)
                                {
                                    resultCellStateTypeId = ((int)EnumCellStateType.Contaminated);
                                }
                                else
                                    resultCellStateTypeId = myCell.CellStateTypeId;
                                
                                //decrease the timer
                                newIterationItem.ImmunityTimerValue = (previousIterationItem.ImmunityTimerValue > 0 ? previousIterationItem.ImmunityTimerValue - 1 : 0);
                            }                            
                            else
                                resultCellStateTypeId = tentativeResultCellStateTypeId;
                        }
                        else
                        {   // Undeterministic (We found multiple transition rules that can be applied to the cells but their result state are different)
                            // The Code decision is to keep the previous transition result but we flag that the cell has an issue                           
                            resultCellStateTypeId = myCell.CellStateTypeId;
                            //even for underterministic case, we still decrease the immunity timer when the previous state has a timer value set
                            if (previousIterationItem.CellStateTypeId == ((int)EnumCellStateType.Decontaminated))
                                newIterationItem.ImmunityTimerValue = (previousIterationItem.ImmunityTimerValue > 0 ? previousIterationItem.ImmunityTimerValue - 1 : 0);

                        }
                    }
                    else
                    {
                        if (resultCellStateTypeId == ((int)EnumCellStateType.Decontaminated))
                        {
                            newIterationItem.ImmunityTimerValue = (previousIterationItem.ImmunityTimerValue>0 ? previousIterationItem.ImmunityTimerValue-1 : 0);
                        }
                    }

                    //setting the final result state
                    newIterationItem.CellStateTypeId = resultCellStateTypeId;

                    Tuple<Guid, int> newIterationItemKey= new Tuple<Guid,int>(myCell.CellId, newStep);
                    this.SystemStateDictionary[newIterationItemKey] = newIterationItem;                        
                }
            }
        }

        /// <summary>
        /// Gets the simulation iteration item.
        /// </summary>
        /// <param name="cellId">The cell id.</param>
        /// <param name="step">The step.</param>
        /// <param name="createIfNull">if set to <c>true</c> [create if null].</param>
        /// <returns></returns>
        public SimulationIterationItem GetSimulationIterationItem(Guid cellId, int step, bool createIfNull)
        {
            Tuple<Guid, int> existingTupleKey = this.SystemStateDictionary.Keys.Where(tuple => tuple.Item1.CompareTo(cellId) == 0 && tuple.Item2 == step).SingleOrDefault();

            if (existingTupleKey == null)
            {
             if(createIfNull)
                    return new SimulationIterationItem(cellId, step);
                else
                    return null;
            }                
            else
            {
                return this.SystemStateDictionary[existingTupleKey];
            }
        }



        /// <summary>
        /// Doeses the cell configuration satisfy transition rule.
        /// </summary>
        /// <param name="myCell">My cell.</param>
        /// <param name="rule">The rule.</param>
        /// <param name="cellularAutomata">The cellular automata.</param>
        /// <returns></returns>
        private bool DoesCellConfigurationSatisfyTransitionRule(Cell myCell, TransitionRule rule, CellularAutomata cellularAutomata)
        {            
            if (myCell.CellStateTypeId != rule.CurrentCellStateTypeId)
                return false;

            // we now validate that the neighbourhood of myCell respect the neighbourhood defined in the transition rule.
            // the algorithm proceed by elimination
            int numberOfRows = cellularAutomata.Height;
            int numberOfColumns = cellularAutomata.Width;

            foreach (NeighbourhoodStateConfiguration ruleStateConfiguration in rule.NeighbourhoodStateConfigurations)
            {
                if (((EnumCellStateType)ruleStateConfiguration.CellStateTypeId) != EnumCellStateType.Generic)
                {
                    //int xIndexNeighbour = myCell.x + ruleStateConfiguration.x_offset;
                    //int yIndexNeighbour = myCell.y + ruleStateConfiguration.y_offset;
                    //int zIndexNeighbour = myCell.z + ruleStateConfiguration.z_offset;

                    ////find the cell pointed by these coordinates. TODO make it complete
                    //int neighbourCellStateTypeId;
                    
                    //bool doesXIndexExist = cellularAutomata.CircularVertical   || (xIndexNeighbour>=0 && xIndexNeighbour<numberOfRows);
                    //bool doesYIndexExist = cellularAutomata.CircularHorizontal || (yIndexNeighbour>=0 && yIndexNeighbour<numberOfColumns);
                    //bool doesZIndexExist = true;
                                                    
                    //if (doesXIndexExist && doesYIndexExist && doesZIndexExist)
                    //{
                    //    neighbourCellStateTypeId = cellularAutomata.CellularAutomataAsArray[
                    //        EntityHelper.MathMod(xIndexNeighbour,numberOfRows) , 
                    //        EntityHelper.MathMod(yIndexNeighbour, numberOfColumns),
                    //        zIndexNeighbour].CellStateTypeId;
                    //}
                    //else
                    //{
                    //    neighbourCellStateTypeId = this.OutOfBoundCellStateTypeId;
                    //}

                    int neighbourCellStateTypeId = EntityHelper.GetNeighbourCellStateTypeId(myCell, 
                        cellularAutomata.CellularAutomataAsArray, 
                        ruleStateConfiguration, 
                        cellularAutomata.CircularVertical, 
                        cellularAutomata.CircularHorizontal, 
                        this.OutOfBoundCellStateTypeId); 

                    if (ruleStateConfiguration.CellStateTypeId != neighbourCellStateTypeId)
                        return false;
                }
            }

            //we found nothing that contradicts the state so it is eligible
            return true;
        }

        


        /// <summary>
        /// Refreshes the cellular automata cells.
        /// </summary>
        /// <param name="step">The step.</param>
        private void RefreshCellularAutomataCells(int step)
        {                       
            foreach (Cell cell in this.CellularAutomata.Cells)
            {
                Tuple<Guid, int> key = this.SystemStateDictionary.Keys.First(tuple => tuple.Item1.CompareTo(cell.CellId) == 0 && tuple.Item2 == step);
                cell.SimulationIterationItem = this.SystemStateDictionary[key];
            }
            
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Raises the property changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
        

        private bool _CanPlayNext;
        public bool CanPlayNext
        {
            get
            {
                return _CanPlayNext;
            }
            set
            {
                if (_CanPlayNext != value)
                {
                    _CanPlayNext = value;
                    this.RaisePropertyChanged("CanPlayNext");
                }
            }
        }


        private bool _CanPlayPrevious;
        public bool CanPlayPrevious
        {
            get
            {
                return _CanPlayPrevious;
            }
            set
            {
                if (_CanPlayPrevious != value)
                {
                    _CanPlayPrevious = value;
                    this.RaisePropertyChanged("CanPlayPrevious");
                }
            }
        }

        private bool _CanPlayFirst;
        public bool CanPlayFirst
        {
            get
            {
                return _CanPlayFirst;
            }
            set
            {
                if (_CanPlayFirst != value)
                {
                    _CanPlayFirst = value;
                    this.RaisePropertyChanged("CanPlayFirst");
                }
            }
        }

        private bool _CanPlayLast;
        public bool CanPlayLast
        {
            get
            {
                return _CanPlayLast;
            }
            set
            {
                if (_CanPlayLast != value)
                {
                    _CanPlayLast = value;
                    this.RaisePropertyChanged("CanPlayLast");
                }
            }
        }
        
    }
}
